[アップデート] AWS Copilot に静的サイト用のサービスデプロイ機能が追加されました
いわさです。
AWS Copilot という CLI ツールを使うことで、ECS や App Runner などのコンテナワークロードのインフラストラクチャ一式を迅速に構築することが出来ます。
本日の AWS Copilot のアップデートで Copilot で作成するサービスタイプに「静的サイト」を選択することが出来るようになりました。
これまでは AWS Copilot でサポートしていたのはサーバーサイドの動的ウェブサイトまででした。
そのため、Copilot のワークショップなどでも、次のように SPA のフロントエンド部分はコンテナ化してデプロイしていました。(あるいは手動で CloudFront や S3 を構成してフロントエンド部分を個別にデプロイする必要がありました)
今回追加されたサービスタイプ「静的サイト」を使うと、従来のインフラストラクチャに加えて静的コンテンツを CloudFront + S3 へデプロイすることが出来るようになります。
単純な静的コンテンツをホスティングしてみる
静的サイトタイプは AWS Colito v1.28.0 以上から利用が可能です。
事前に以下に従ってインストール or アップデートしましょう。
% copilot --version copilot version: v1.28.0 % copilot init --help Create a new ECS or App Runner application. Usage copilot init [flags] Flags -a, --app string Name of the application. --deploy Deploy your service or job to a "test" environment. -d, --dockerfile string Path to the Dockerfile. Cannot be specified with --image. -h, --help help for init -i, --image string The location of an existing Docker image. Cannot be specified with --dockerfile or --build-context. -n, --name string Name of the service or job. --port uint16 The port on which your service listens. --retries int Optional. The number of times to try restarting the job on a failure. --schedule string The schedule on which to run this job. Accepts cron expressions of the format (M H DoM M DoW) and schedule definition strings. For example: "0 * * * *", "@daily", "@weekly", "@every 1h30m". AWS Schedule Expressions of the form "rate(10 minutes)" or "cron(0 12 L * ? 2021)" are also accepted. --tag string Optional. The container image tag. --timeout string Optional. The total execution time for the task, including retries. Accepts valid Go duration strings. For example: "2h", "1h30m", "900s". -t, --type string Type of job or svc to create. Must be one of: "Request-Driven Web Service", "Load Balanced Web Service", "Backend Service", "Worker Service", "Static Site", "Scheduled Job".
まず、ローカルフォルダに静的サイトサービスで使用する静的コンテンツを用意しておきます。
今回は次のような単純なコンテンツindex.html
を用意しました。
あとはcopilot init
コマンドでタイプにStatic Site
を指定します。
途中、コンテンツのパスを聞かれるので先程静的コンテンツを格納したディレクトリを指定します。
% copilot init -t "Static Site" Note: It's best to run this command in the root of your Git repository. Welcome to the Copilot CLI! We're going to walk you through some questions to help you get set up with a containerized application on AWS. An application is a collection of containerized services that operate together. Use existing application: No Application name: hoge0526staticapp Service name: hoge0526staticservice Custom Path to Source: static-content Another: No Ok great, we'll set up a Static Site named hoge0526staticservice in application hoge0526staticapp. ✔ Proposing infrastructure changes for stack hoge0526staticapp-infrastructure-roles - Creating the infrastructure for stack hoge0526staticapp-infrastructure-roles [create complete] [63.1s] - A StackSet admin role assumed by CloudFormation to manage regional stacks [create complete] [26.2s] - An IAM role assumed by the admin role to create ECR repositories, KMS keys, and S3 buckets [create complete] [27.5s] ✔ The directory copilot will hold service manifests for application hoge0526staticapp. Docker daemon is not responsive; Copilot won't detect and populate the "platform" field in the manifest. ✔ Wrote the manifest for service hoge0526staticservice at copilot/hoge0526staticservice/manifest.yml Your manifest contains configurations like your container size and port. - Update regional resources with stack set "hoge0526staticapp-infrastructure" [succeeded] [0.0s] All right, you're all set for local development. Would you like to deploy a test environment? [? for help] (y/N)
test
という名前の環境にデプロイするか聞かれていますね。
この時点で次のように静的サイト用のマニフェストファイルが作成されています。
# The manifest for the "hoge0526staticservice" service. # Read the full specification for the "Static Site" type at: # https://aws.github.io/copilot-cli/docs/manifest/static-site/ # Your service name will be used in naming your resources like S3 buckets, etc. name: hoge0526staticservice type: Static Site files: - source: static-content recursive: true # You can override any of the values defined above by environment. # environments: # test: # files: # - source: './blob' # recursive: true # destination: 'assets' # exclude: '*' # reinclude: # - '*.txt' # - '*.png'
対話の中で指定した静的コンテンツへのパスがコンテンツソースとして設定されていますね。
デフォルトで設定されるのはそれだけです。
静的コンテンツのマニフェストファイルの詳細は以下を確認してください。
この内容から推測するに格納してあるコンテンツをアップするだけのように見えるので、フロントエンドのビルドとかはさすがに無理そうです。
そのあたりは事前に実行しておく必要がありそうですが、別途試してみたいと思います。
作成されるスタック
このあとデプロイまで実行します。
: All right, you're all set for local development. Deploy: Yes ✔ Wrote the manifest for environment test at copilot/environments/test/manifest.yml - Update regional resources with stack set "hoge0526staticapp-infrastructure" [succeeded] [0.0s] - Update regional resources with stack set "hoge0526staticapp-infrastructure" [succeeded] [128.9s] - Update resources in region "ap-northeast-1" [create complete] [127.0s] - KMS key to encrypt pipeline artifacts between stages [create complete] [123.8s] - S3 Bucket to store local artifacts [create complete] [2.0s] ✔ Proposing infrastructure changes for the hoge0526staticapp-test environment. - Creating the infrastructure for the hoge0526staticapp-test environment. [create complete] [60.3s] - An IAM Role for AWS CloudFormation to manage resources [create complete] [26.3s] - An IAM Role to describe resources in your environment [create complete] [24.6s] ✔ Provisioned bootstrap resources for environment test in region ap-northeast-1 under application hoge0526staticapp. ✔ Provisioned bootstrap resources for environment test. ✔ Proposing infrastructure changes for the hoge0526staticapp-test environment. - Creating the infrastructure for the hoge0526staticapp-test environment. [update complete] [73.0s] - An ECS cluster to group your services [create complete] [7.3s] - A security group to allow your containers to talk to each other [create complete] [0.0s] - An Internet Gateway to connect to the public internet [create complete] [14.7s] - Private subnet 1 for resources with no internet access [create complete] [0.0s] - Private subnet 2 for resources with no internet access [create complete] [3.4s] - A custom route table that directs network traffic for the public subnets [create complete] [11.3s] - Public subnet 1 for resources that can access the internet [create complete] [2.1s] - Public subnet 2 for resources that can access the internet [create complete] [2.1s] - A private DNS namespace for discovering services within the environment [create complete] [43.1s] - A Virtual Private Cloud to control networking of your AWS resources [create complete] [8.8s] ✔ Proposing infrastructure changes for stack hoge0526staticapp-test-hoge0526staticservice - Creating the infrastructure for stack hoge0526staticapp-test-hoge0526staticservice [create complete] [274.6s] - A bucket policy to grant CloudFront read access to the Static Site bucket [create complete] [0.0s] - An S3 Bucket to store the static site's assets [create complete] [22.3s] - A CloudFront distribution for global content delivery [create complete] [226.6s] - Access control to make the content in the S3 bucket only accessible through CloudFront [create complete] [7.7s] - CloudFront Function to rewrite viewer request to index.html [create complete] [4.4s] - An IAM Role for the state machine that moves source files to the S3 bucket [create complete] [44.6s] - A state machine that moves source files to the S3 bucket [create complete] [0.0s] - A policy that gives the Env Manager role access to this site's S3 Bucket [create complete] [40.0s] - A custom resource that starts the process of moving files to the S3 bucket [create complete] [3.8s] - An IAM Role for the lambda that starts the process of moving files to the S3 bucket [create complete] [45.2s] - A lambda that starts the process of moving files to the S3 bucket [create complete] [8.8s] ✔ Deployed service hoge0526staticservice. Recommended follow-up action: - You can access your service at d1qtih0mnc42v2.cloudfront.net over the internet. - Be a part of the Copilot ✨community✨! Ask or answer a question, submit a feature request... Visit ? https://aws.github.io/copilot-cli/community/get-involved/ to see how!
デプロイが完了しました。
AWS Copilot からデプロイを行うと CloudFormation スタックが作成されます。
今回の機能でいくつか作成されるスタックはいくつかあるのですが、主要なものは以下の 2 つです。
上記のhoge0526staticapp-test
は Copilot でデプロイする環境のベースとなる共通ネットワークや ECS クラスターなどがデプロイされます。
今回は静的コンテンツのため VPC リソースは使わんだろうと思っていたのですが、環境リソースについては従来同様にデプロイされるということがわかりました。
今回の機能で新しくデプロイされるようになったのはhoge0526staticapp-test-hoge0526staticservice
のほうですね(名前...)
こちらは次を見て頂くとわかるように主に CloudFront + S3 で構成されています。
上記の赤枠のリソースで StepFunctions などが登場していて、おや?という感じだと思いますが、これはローカルコンテンツをデプロイするためのカスタムリソース用に使われているもののようです。内容としてはコンテンツコピーのためだけに使われていました。
デプロイされたスタックから CloudFront + S3 関係だけを抜粋してみました。
次のようなものがデプロイされています。
: Resources: Bucket: Metadata: aws:copilot:description: An S3 Bucket to store the static site's assets Type: AWS::S3::Bucket Properties: VersioningConfiguration: Status: Enabled AccessControl: Private BucketEncryption: ServerSideEncryptionConfiguration: - ServerSideEncryptionByDefault: SSEAlgorithm: AES256 PublicAccessBlockConfiguration: BlockPublicAcls: true BlockPublicPolicy: true OwnershipControls: Rules: - ObjectOwnership: BucketOwnerEnforced BucketPolicyForCloudFront: Metadata: 'aws:copilot:description': 'A bucket policy to grant CloudFront read access to the Static Site bucket' Type: AWS::S3::BucketPolicy Properties: Bucket: !Ref Bucket PolicyDocument: Version: 2012-10-17 Statement: - Sid: ForceHTTPS Effect: Deny Principal: "*" Action: s3:* Resource: - !Sub ${Bucket.Arn} - !Sub ${Bucket.Arn}/* Condition: Bool: aws:SecureTransport: false - Sid: AllowCloudFrontServicePrincipalReadOnly Effect: Allow Principal: Service: cloudfront.amazonaws.com Action: s3:GetObject Resource: - !Sub - arn:${AWS::Partition}:s3:::${bucket} - bucket: !Ref Bucket - !Sub - arn:${AWS::Partition}:s3:::${bucket}/* - bucket: !Ref Bucket Condition: StringEquals: AWS:SourceArn: !Sub - arn:${AWS::Partition}:cloudfront::${AWS::AccountId}:distribution/${cfDistributionID} - cfDistributionID: !Ref CloudFrontDistribution CloudFrontOriginAccessControl: Metadata: 'aws:copilot:description': 'Access control to make the content in the S3 bucket only accessible through CloudFront' Type: AWS::CloudFront::OriginAccessControl Properties: OriginAccessControlConfig: Description: !Sub 'Access control for static s3 origin for ${AppName}-${EnvName}-${WorkloadName}' # Truncate the name to allow at most 64 characters. Name: hoge0526staticapp-test-hoge0526staticservice OriginAccessControlOriginType: s3 SigningBehavior: always SigningProtocol: sigv4 CloudFrontViewerRequestRewriteFunction: Metadata: 'aws:copilot:description': 'CloudFront Function to rewrite viewer request to index.html' Type: AWS::CloudFront::Function Properties: AutoPublish: true FunctionCode: | function handler(event){var request=event.request;var uri=request.uri;if(uri.endsWith('/')){request.uri+='index.html'}else if(!uri.includes('.')){request.uri+='/index.html'}return request} FunctionConfig: Comment: CloudFront Function to rewrite viewer request to index.html Runtime: cloudfront-js-1.0 # Truncate the name to allow at most 64 characters. Name: hoge0526staticapp-test-hoge0526staticservice CloudFrontDistribution: Metadata: 'aws:copilot:description': 'A CloudFront distribution for global content delivery' Type: AWS::CloudFront::Distribution Properties: DistributionConfig: DefaultCacheBehavior: Compress: true AllowedMethods: ["GET", "HEAD"] FunctionAssociations: - EventType: viewer-request FunctionARN: !GetAtt CloudFrontViewerRequestRewriteFunction.FunctionARN ViewerProtocolPolicy: redirect-to-https CachePolicyId: 658327ea-f89d-4fab-a63d-7e88639e58f6 # See https://go.aws/3bJid3k TargetOriginId: !Sub 'copilot-${AppName}-${EnvName}-${WorkloadName}' Enabled: true IPV6Enabled: true Origins: - Id: !Sub 'copilot-${AppName}-${EnvName}-${WorkloadName}' DomainName: !GetAtt Bucket.RegionalDomainName OriginAccessControlId: !Ref CloudFrontOriginAccessControl # Workaround for using Origin Access Control as Origin Access Identity is still # required when the origin is an S3 bucket. S3OriginConfig: OriginAccessIdentity: '' :
S3 ではウェブホスティング機能は使われていないですね。
通常の構成に CloudFront から OAC でアクセス制御しています。
そしてウェブに必要なリダイレクトなどの処理は上記のCloudFrontViewerRequestRewriteFunction
で作成されている CloudFront Functions の機能でエッジ処理させる仕組みをとっています。
こういうスタック見ると、静的ホスティングの際の参考にもなるのでおもしろいですね
コンテンツへアクセスする
最後に、AWS Copilot CLI で出力された URL へアクセスしてみます。
% curl https://d1qtih0mnc42v2.cloudfront.net/ hoge0526
問題なくアクセス出来ましたね。
さいごに
本日は AWS Copilot に静的サイト用のサービスデプロイ機能が追加されたので使ってみました。
基礎のクラスターなどもデフォルトでデプロイされるので単純な静的ウェブサイトをホスティングする用途ではなく、あくまでも ECS や App Runner などのコンテナバックエンドを利用するフロントエンドをホスティングするためのオプションとして今回の機能が追加された感じですね。